using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Reflection;

namespace SharpObjects.EventBus
{
    public static class EventBusManager
    {
        public const string guid = "_{313DD131-BA38-4370-B301-44110922F64B}";
        public const string EventBusId = "event_bus" + guid;

        public const string ConfigurationFileName = "SharpObjects.EventBus.config";
        public const string EventBusDescriptorFileName = "SharpObjects.EventBusDescriptor.json";
        private const string SharpObjectsApplicationFolderTemplate = "{0}\\SharpObjects\\";

        private static EventBusDescirptor eventBusDescriptor = null;
        private static IEventBus eventBusInstance = null;

        public static IEventBus EventBus
        {
            get
            {
                if (System.Web.HttpContext.Current == null)
                {
                    if(eventBusInstance == null)
                    {
                        Initialize();
                    }

                    return eventBusInstance;
                }
                else
                {
                    if (eventBusDescriptor.IsApplicationWide)
                    {

                        IEventBus eventBus = (IEventBus)System.Web.HttpContext.Current.Application[EventBusId];

                        if(eventBus == null)
                        {
                            Initialize();
                        }

                        return (IEventBus)System.Web.HttpContext.Current.Application[EventBusId];
                    }
                    else
                    {
                        IEventBus eventBus = (IEventBus)System.Web.HttpContext.Current.Session[EventBusId];

                        if(eventBus == null)
                        {
                            Initialize();
                        }

                        return (IEventBus)System.Web.HttpContext.Current.Session[EventBusId];
                    }
                }  
            }
        }

        private static void SetEventBus(IEventBus eventBus)
        {
            if (System.Web.HttpContext.Current == null)
            {
                eventBusInstance = eventBus;
            }
            else
            {
                if(eventBusDescriptor.IsApplicationWide)
                {
                    System.Web.HttpContext.Current.Application[EventBusId] = eventBus;
                }
                else
                {
                    System.Web.HttpContext.Current.Session[EventBusId] = eventBus;
                }
            }        
        }

        /*
        SharpObjects.EventBus.config file format:

        <?xml version="1.0" encoding="utf-8" ?>
        <configuration>
          <event_bus>
            <assembly>SharpObjects.EventBus.dll</assembly>
            <type>SharpObjects.EventBus.EventBus</type>
            <!-- arguments to pass to the constructor if any -->
            <arguments>
            </arguments>
                <argiment>argument 1</argument>
                <argiment>argument 2</argument>
          </event_bus>
        </configuration>
        
        
        SharpObjects.EventBusDescriptor.json file sample:
        
        {
	        "AssemblyPath":"SharpObjects.EventBus.dll",
	        "TypeName":"SharpObjects.EventBus.EventBus",
	        "Arguments":null
        }
        
        */

        static void Initialize()
        {
            string location = "SharpObjects.EventBus.EventBusFactory()";

            try
            {
                eventBusDescriptor = new EventBusDescirptor()
                                         {
                                             AssemblyPath = "SharpObjects.EventBus.dll",
                                             TypeName = "SharpObjects.EventBus.EventBus"
                                         };
                SetEventBus(new EventBus());
                

                Assembly currentAssembly = Assembly.GetExecutingAssembly();

                if(currentAssembly != null)
                {
                    string configFilePath = currentAssembly.Location;
                    string assemblyDirectory = Path.GetDirectoryName(configFilePath);
                    configFilePath = assemblyDirectory + "\\" + ConfigurationFileName;
                    string eventBusDescriptorFilePath = assemblyDirectory + "\\" + EventBusDescriptorFileName;


                    if(File.Exists(eventBusDescriptorFilePath))
                    {
                        eventBusDescriptor = GetEventBusDescriptorFromJsonDescriptor(eventBusDescriptorFilePath,
                                                                                     assemblyDirectory);    
                    }

                    if (eventBusDescriptor == null && File.Exists(configFilePath))
                    {
                        eventBusDescriptor = GetEventBusDescriptorFromXmlConfigFile(configFilePath, assemblyDirectory);
                    }

                    if (eventBusDescriptor != null)
                    {
                        Assembly assembly = Assembly.LoadFrom(eventBusDescriptor.AssemblyPath);

                        if (assembly == null)
                        {
                            return;
                        }

                        Type type = assembly.GetType(eventBusDescriptor.TypeName);

                        if (type == null)
                        {
                            return;
                        }

                        IEventBus eventBus = (IEventBus)Activator.CreateInstance(type, eventBusDescriptor.Arguments);
                        SetEventBus(eventBus);
                    }
                }
            }
            catch (Exception ex)
            {
                string message = string.Format("SharpObjects.EventBus ERROR.\nLOCATION:{0}\n\n {1}\n\nSTACK TRACE:\n\n{2}",
                                               location, ex.Message, ex.StackTrace);
                EventLog.WriteEntry("SharpObjects", message , EventLogEntryType.Error);
            }
        }

        private static EventBusDescirptor GetEventBusDescriptorFromJsonDescriptor(string eventBusDescriptorFilePath, string assemblyDirectory)
        {
            string location = "SharpObjects.EventBus.GetEventBusDescriptorFromJsonDescriptor()";

            try
            {
                string json = File.ReadAllText(eventBusDescriptorFilePath);
                EventBusDescirptor eventBusDescriptor =
                    (EventBusDescirptor) JsonSerialization.Deserialize<EventBusDescirptor>(json);

                if (!Path.IsPathRooted(eventBusDescriptor.AssemblyPath))
                {
                    eventBusDescriptor.AssemblyPath = assemblyDirectory + "\\" + eventBusDescriptor.AssemblyPath;
                }

                if (!File.Exists(eventBusDescriptor.AssemblyPath))
                {
                    return null;
                }

                return eventBusDescriptor;
            }
            catch (Exception ex)
            {
                string message = string.Format("SharpObjects.EventBus ERROR.\nLOCATION:{0}\n\n {1}\n\nSTACK TRACE:\n\n{2}",
                                               location, ex.Message, ex.StackTrace);
                EventLog.WriteEntry("SharpObjects", message, EventLogEntryType.Error);
            }

            return null;
        }

        private static EventBusDescirptor GetEventBusDescriptorFromXmlConfigFile(string configFilePath, string assemblyDirectory)
        {
            string location = "SharpObjects.EventBus.GetEventBusDescriptorFromXmlConfigFile()";

            try
            {
                EventBusDescirptor eventBusDescirptor = new EventBusDescirptor();

                XElement root = XElement.Load(configFilePath);

                XElement assemblyElement = root.XPathSelectElement("/event_bus/assembly");
                XElement typeElement = root.XPathSelectElement("/event_bus/type");
                XElement isApplicationwideElement = root.XPathSelectElement("/event_bus/is_applicatoinwide");

                if (assemblyElement == null || typeElement == null)
                {
                    return null;
                }

                string assemblyPath = assemblyElement.Value.Trim();

                if (!Path.IsPathRooted(assemblyPath))
                {
                    assemblyPath = assemblyDirectory + "\\" + assemblyPath;
                }

                if (!File.Exists(assemblyPath))
                {
                    return null;
                }

                eventBusDescirptor.AssemblyPath = assemblyPath;
                eventBusDescirptor.TypeName = typeElement.Value.Trim();

                if(isApplicationwideElement != null)
                {
                    if(isApplicationwideElement.Value.Trim().ToLower() == "false")
                    {
                        eventBusDescirptor.IsApplicationWide = false;
                    }
                }

                IEnumerable<XElement> argumentCollection =
                    root.XPathSelectElements("/event_bus/arguments/argument");
                List<object> argumentList = new List<object>();
                argumentList.AddRange(argumentCollection);

                if (argumentList.Count > 0)
                {
                    foreach (XElement argumentElement in argumentCollection)
                    {
                        argumentList.Add(argumentElement.Value);
                    }

                    eventBusDescirptor.Arguments = argumentList.ToArray();
                }

                return eventBusDescirptor;
            }
            catch (Exception ex)
            {
                string message = string.Format("SharpObjects.EventBus ERROR.\nLOCATION:{0}\n\n {1}\n\nSTACK TRACE:\n\n{2}",
                                               location, ex.Message, ex.StackTrace);
                EventLog.WriteEntry("SharpObjects", message, EventLogEntryType.Error);
            }

            return null;
        }

    }
}